﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using Cmd.Internal;
using System.Collections;
using System.Reflection;
using System.Collections.ObjectModel;
using System.IO;
using Cmd.TypeConverters;

namespace Cmd
{
    public class OptionSet
    {
        internal UniqueCollection<OptionGroup> _groups;
        internal CommandLineOptionCollection _innerList;
        internal UniqueCollection<string> _errors;
        internal bool _isValid = true;
        internal string _activeGroup;

        public OptionSet()
        {
            _innerList = new CommandLineOptionCollection();
            _errors = new UniqueCollection<string>();
            _groups = new UniqueCollection<OptionGroup>();
        }

        public ReadOnlyCollection<OptionGroup> GetGroups()
        {
            return new ReadOnlyCollection<OptionGroup>(_groups);
        }

        public UniqueCollection<string> GetErrors()
        {
            return _errors;
        }

        public CommandLineOptionCollection GetOptions()
        {
            return _innerList;
        }

        public string GetGroup()
        {
            return _activeGroup;
        }

        public virtual bool Validate(out string[] errorMesages)
        {
            errorMesages = null;
            return _errors.Count == 0;
        }

        internal void ShowHelp(OptionAttribute[] options)
        {
            //Console.OutputEncoding = System.Text.Encoding.GetEncoding(1252);
            foreach (string line in GetHelp(options))
            {
                Console.WriteLine(line);
            }
        }

        protected virtual string[] GetUsageScenarios()
        {
            return null;
        }

        public virtual ITypeConverter[] GetCustomConverters()
        {
            return null;
        }

        protected virtual string[] GetHelp(OptionAttribute[] options)
        {
            List<string> list = new List<string>();
            string ln = null;


            if (!_isValid)
            {
                list.Add("The arguments are not valid:");
                foreach (string error in _errors)
                {
                    list.Add(string.Format("  * {0}", error));
                }
                list.Add("");
            }

            string description = GetDescription();
            if (!string.IsNullOrWhiteSpace(description) && _isValid)
            {
                list.Add("Description:");
                string[] descRawLines = description.Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
                foreach (string dRawLine in descRawLines)
                {
                    List<string> descLines = string.Concat("    ", dRawLine).Split(Console.BufferWidth - 4, StringSplitOptions.RemoveEmptyEntries).ToList();
                    foreach (string d in descLines)
                    {
                        list.Add(d);
                    }
                }
                list.Add("");
            }

            string[] usageList = GetUsageScenarios();
            if (usageList != null && usageList.Length > 0)
            {
                string appName = Path.GetFileNameWithoutExtension(Assembly.GetEntryAssembly().Location);
                ln = "Usage: ";
                foreach (string usage in usageList)
                {
                    ln = !string.IsNullOrEmpty(ln) ? ln + appName + " " + usage.Trim() : " ".Multiply(7) + appName + " " + usage.Trim();
                    list.Add(ln);
                    ln = null;
                }
                list.Add("");
            }

            //List<OptionGroupItem> optionList = this._groups.SelectMany(grp => grp._options).Distinct(new OptionGroupItemEqComparer()).ToList();
            if (options == null || options.Length == 0)
            {
                return list.ToArray();
            }
            list.Add("Options:");

            int helpLeftPos = options.Max(item =>
                string.Format("    -{0}{1}{2}",
                    item.Command,
                    string.IsNullOrWhiteSpace(item.Alias) || item.Command == item.Alias ? "" : string.Format("|-{0} ", item.Alias),
                    string.IsNullOrWhiteSpace(item.Name) ? "" : string.Format(" {0}", item.Name)).Length) + 5;

            bool sameLine = true;
            if (Console.BufferWidth - helpLeftPos < 20)
            {
                helpLeftPos = 5;
                sameLine = false;
            }
            foreach (OptionAttribute item in options)
            {
                string optText = string.Format("    -{0}{1}{2}",
                    item.Command,
                    string.IsNullOrWhiteSpace(item.Alias) || item.Command == item.Alias ? "" : string.Format("|-{0} ", item.Alias),
                    string.IsNullOrWhiteSpace(item.Name) ? "" : string.Format(" {0}", item.Name));
                if (sameLine)
                {
                    ln = optText;
                }
                else
                {
                    ln = string.Empty;
                    list.Add(optText);
                }

                IEnumerable<string> lines = item.Description.Split(Console.BufferWidth - helpLeftPos);
                foreach (string line in lines)
                {
                    ln += line.PadLeft(helpLeftPos - 1 + line.Length - ln.Length);
                    list.Add(ln);
                    ln = string.Empty;
                }
                if (item.DefaultValue != null)
                {
                    ln = string.Format("Default value is '{0}'.", item.DefaultValue);
                    list.Add(ln.PadLeft(helpLeftPos - 1 + ln.Length));
                }
            }

            return list.ToArray();
        }

        public virtual string[] GetAssemblyInfo()
        {
            List<string> list = new List<string>();

            Assembly a = Assembly.GetEntryAssembly();

            string ln = a.GetAssemblyInfo(AssemblyInfoSpec.Title);
            if (!string.IsNullOrWhiteSpace(ln))
            {
                list.Add(ln + " v. " + a.GetAssemblyInfo(AssemblyInfoSpec.Version));
            }
            ln = a.GetAssemblyInfo(AssemblyInfoSpec.Description);
            if (!string.IsNullOrWhiteSpace(ln))
            {
                list.Add(ln);
            }
            ln = a.GetAssemblyInfo(AssemblyInfoSpec.CopyrightHolder);
            if (!string.IsNullOrWhiteSpace(ln))
            {
                list.Add(ln);
            }

            return list.ToArray();
        }

        public virtual string GetDescription()
        {
            return null;
        }

        public override string ToString()
        {
            List<string> args = _innerList.Select(item => string.Format("/{0}", item.ToString(true))).ToList();
            return string.Join(" ", args);
        }
    }
}
